use alloc::{
    borrow::Cow,
    string::{String, ToString},
    sync::Arc,
    vec::Vec,
};
use core::convert::Infallible;
use std::io::Write as _;

use crate::{
    command::{
        ArcCommand, ArcComputeCommand, ArcPassTimestampWrites, ArcReferences, ArcRenderCommand,
        BasePass, ColorAttachments, Command, ComputeCommand, PointerReferences, RenderCommand,
        RenderPassColorAttachment, ResolvedRenderPassDepthStencilAttachment,
    },
    id::{markers, PointerId},
    storage::StorageItem,
};

use super::{
    Action, TraceBindGroupDescriptor, TraceComputePipelineDescriptor,
    TraceGeneralRenderPipelineDescriptor, FILE_NAME,
};

pub(crate) fn new_render_bundle_encoder_descriptor(
    label: crate::Label<'_>,
    context: &crate::device::RenderPassContext,
    depth_read_only: bool,
    stencil_read_only: bool,
) -> crate::command::RenderBundleEncoderDescriptor<'static> {
    crate::command::RenderBundleEncoderDescriptor {
        label: label.map(|l| Cow::from(l.to_string())),
        color_formats: Cow::from(context.attachments.colors.to_vec()),
        depth_stencil: context.attachments.depth_stencil.map(|format| {
            wgt::RenderBundleDepthStencil {
                format,
                depth_read_only,
                stencil_read_only,
            }
        }),
        sample_count: context.sample_count,
        multiview: context.multiview_mask,
    }
}

#[derive(Debug)]
pub struct Trace {
    path: std::path::PathBuf,
    file: std::fs::File,
    config: ron::ser::PrettyConfig,
    binary_id: usize,
}

impl Trace {
    pub fn new(path: std::path::PathBuf) -> Result<Self, std::io::Error> {
        log::debug!("Tracing into '{path:?}'");
        let mut file = std::fs::File::create(path.join(FILE_NAME))?;
        file.write_all(b"[\n")?;
        Ok(Self {
            path,
            file,
            config: ron::ser::PrettyConfig::default(),
            binary_id: 0,
        })
    }

    pub fn make_binary(&mut self, kind: &str, data: &[u8]) -> String {
        self.binary_id += 1;
        let name = std::format!("data{}.{}", self.binary_id, kind);
        let _ = std::fs::write(self.path.join(&name), data);
        name
    }

    pub(crate) fn add(&mut self, action: Action<'_, PointerReferences>)
    where
        for<'a> Action<'a, PointerReferences>: serde::Serialize,
    {
        match ron::ser::to_string_pretty(&action, self.config.clone()) {
            Ok(string) => {
                let _ = writeln!(self.file, "{string},");
            }
            Err(e) => {
                log::warn!("RON serialization failure: {e:?}");
            }
        }
    }
}

impl Drop for Trace {
    fn drop(&mut self) {
        let _ = self.file.write_all(b"]");
    }
}

pub(crate) trait IntoTrace {
    type Output;
    fn into_trace(self) -> Self::Output;

    fn to_trace(&self) -> Self::Output
    where
        Self: Sized + Clone,
    {
        self.clone().into_trace()
    }
}

impl<T: StorageItem> IntoTrace for Arc<T> {
    type Output = PointerId<T::Marker>;
    fn into_trace(self) -> Self::Output {
        self.to_trace()
    }

    fn to_trace(&self) -> Self::Output {
        PointerId::from(self)
    }
}

impl IntoTrace for ArcCommand {
    type Output = Command<PointerReferences>;
    fn into_trace(self) -> Self::Output {
        match self {
            ArcCommand::CopyBufferToBuffer {
                src,
                src_offset,
                dst,
                dst_offset,
                size,
            } => Command::CopyBufferToBuffer {
                src: src.to_trace(),
                src_offset,
                dst: dst.to_trace(),
                dst_offset,
                size,
            },
            ArcCommand::CopyBufferToTexture { src, dst, size } => Command::CopyBufferToTexture {
                src: src.into_trace(),
                dst: dst.into_trace(),
                size,
            },
            ArcCommand::CopyTextureToBuffer { src, dst, size } => Command::CopyTextureToBuffer {
                src: src.into_trace(),
                dst: dst.into_trace(),
                size,
            },
            ArcCommand::CopyTextureToTexture { src, dst, size } => Command::CopyTextureToTexture {
                src: src.into_trace(),
                dst: dst.into_trace(),
                size,
            },
            ArcCommand::ClearBuffer { dst, offset, size } => Command::ClearBuffer {
                dst: dst.to_trace(),
                offset,
                size,
            },
            ArcCommand::ClearTexture {
                dst,
                subresource_range,
            } => Command::ClearTexture {
                dst: dst.to_trace(),
                subresource_range,
            },
            ArcCommand::WriteTimestamp {
                query_set,
                query_index,
            } => Command::WriteTimestamp {
                query_set: query_set.to_trace(),
                query_index,
            },
            ArcCommand::ResolveQuerySet {
                query_set,
                start_query,
                query_count,
                destination,
                destination_offset,
            } => Command::ResolveQuerySet {
                query_set: query_set.to_trace(),
                start_query,
                query_count,
                destination: destination.to_trace(),
                destination_offset,
            },
            ArcCommand::PushDebugGroup(label) => Command::PushDebugGroup(label),
            ArcCommand::PopDebugGroup => Command::PopDebugGroup,
            ArcCommand::InsertDebugMarker(label) => Command::InsertDebugMarker(label),
            ArcCommand::RunComputePass {
                pass,
                timestamp_writes,
            } => Command::RunComputePass {
                pass: pass.into_trace(),
                timestamp_writes: timestamp_writes.map(|tw| tw.into_trace()),
            },
            ArcCommand::RunRenderPass {
                pass,
                color_attachments,
                depth_stencil_attachment,
                timestamp_writes,
                occlusion_query_set,
                multiview_mask,
            } => Command::RunRenderPass {
                pass: pass.into_trace(),
                color_attachments: color_attachments.into_trace(),
                depth_stencil_attachment: depth_stencil_attachment.map(|d| d.into_trace()),
                timestamp_writes: timestamp_writes.map(|tw| tw.into_trace()),
                occlusion_query_set: occlusion_query_set.map(|q| q.to_trace()),
                multiview_mask,
            },
            ArcCommand::BuildAccelerationStructures { blas, tlas } => {
                Command::BuildAccelerationStructures {
                    blas: blas.into_iter().map(|b| b.into_trace()).collect(),
                    tlas: tlas.into_iter().map(|b| b.into_trace()).collect(),
                }
            }
            ArcCommand::TransitionResources {
                buffer_transitions: _,
                texture_transitions: _,
            } => {
                // TransitionResources does not exist in Command, so skip or handle as needed.
                // If you want to ignore, you could panic or return a default.
                panic!("TransitionResources cannot be converted to Command");
            }
        }
    }
}

impl<T: IntoTrace> IntoTrace for wgt::TexelCopyBufferInfo<T> {
    type Output = wgt::TexelCopyBufferInfo<T::Output>;
    fn into_trace(self) -> Self::Output {
        wgt::TexelCopyBufferInfo {
            buffer: self.buffer.into_trace(),
            layout: self.layout,
        }
    }
}

impl<T: IntoTrace> IntoTrace for wgt::TexelCopyTextureInfo<T> {
    type Output = wgt::TexelCopyTextureInfo<T::Output>;
    fn into_trace(self) -> Self::Output {
        wgt::TexelCopyTextureInfo {
            texture: self.texture.into_trace(),
            mip_level: self.mip_level,
            origin: self.origin,
            aspect: self.aspect,
        }
    }
}

impl IntoTrace for ArcPassTimestampWrites {
    type Output = crate::command::PassTimestampWrites<PointerId<markers::QuerySet>>;
    fn into_trace(self) -> Self::Output {
        crate::command::PassTimestampWrites {
            query_set: self.query_set.into_trace(),
            beginning_of_pass_write_index: self.beginning_of_pass_write_index,
            end_of_pass_write_index: self.end_of_pass_write_index,
        }
    }
}

impl IntoTrace for ColorAttachments {
    type Output = ColorAttachments<PointerId<markers::TextureView>>;
    fn into_trace(self) -> Self::Output {
        self.into_iter()
            .map(|opt| {
                opt.map(|att| RenderPassColorAttachment {
                    view: att.view.into_trace(),
                    depth_slice: att.depth_slice,
                    resolve_target: att.resolve_target.map(|r| r.into_trace()),
                    load_op: att.load_op,
                    store_op: att.store_op,
                })
            })
            .collect()
    }
}

impl<TV: IntoTrace> IntoTrace for ResolvedRenderPassDepthStencilAttachment<TV> {
    type Output = ResolvedRenderPassDepthStencilAttachment<TV::Output>;
    fn into_trace(self) -> Self::Output {
        ResolvedRenderPassDepthStencilAttachment {
            view: self.view.into_trace(),
            depth: self.depth,
            stencil: self.stencil,
        }
    }
}

impl IntoTrace for crate::ray_tracing::OwnedBlasBuildEntry<ArcReferences> {
    type Output = crate::ray_tracing::OwnedBlasBuildEntry<PointerReferences>;
    fn into_trace(self) -> Self::Output {
        crate::ray_tracing::OwnedBlasBuildEntry {
            blas: self.blas.into_trace(),
            geometries: self.geometries.into_trace(),
        }
    }
}

impl IntoTrace for crate::ray_tracing::OwnedBlasGeometries<ArcReferences> {
    type Output = crate::ray_tracing::OwnedBlasGeometries<PointerReferences>;
    fn into_trace(self) -> Self::Output {
        match self {
            crate::ray_tracing::OwnedBlasGeometries::TriangleGeometries(geos) => {
                crate::ray_tracing::OwnedBlasGeometries::TriangleGeometries(
                    geos.into_iter().map(|g| g.into_trace()).collect(),
                )
            }
        }
    }
}

impl IntoTrace for crate::ray_tracing::OwnedBlasTriangleGeometry<ArcReferences> {
    type Output = crate::ray_tracing::OwnedBlasTriangleGeometry<PointerReferences>;
    fn into_trace(self) -> Self::Output {
        crate::ray_tracing::OwnedBlasTriangleGeometry {
            size: self.size,
            vertex_buffer: self.vertex_buffer.into_trace(),
            index_buffer: self.index_buffer.map(|b| b.into_trace()),
            transform_buffer: self.transform_buffer.map(|b| b.into_trace()),
            first_vertex: self.first_vertex,
            vertex_stride: self.vertex_stride,
            first_index: self.first_index,
            transform_buffer_offset: self.transform_buffer_offset,
        }
    }
}

impl IntoTrace for crate::ray_tracing::OwnedTlasPackage<ArcReferences> {
    type Output = crate::ray_tracing::OwnedTlasPackage<PointerReferences>;
    fn into_trace(self) -> Self::Output {
        crate::ray_tracing::OwnedTlasPackage {
            tlas: self.tlas.into_trace(),
            instances: self
                .instances
                .into_iter()
                .map(|opt| opt.map(|inst| inst.into_trace()))
                .collect(),
            lowest_unmodified: self.lowest_unmodified,
        }
    }
}

impl IntoTrace for crate::ray_tracing::OwnedTlasInstance<ArcReferences> {
    type Output = crate::ray_tracing::OwnedTlasInstance<PointerReferences>;
    fn into_trace(self) -> Self::Output {
        crate::ray_tracing::OwnedTlasInstance {
            blas: self.blas.into_trace(),
            transform: self.transform,
            custom_data: self.custom_data,
            mask: self.mask,
        }
    }
}

impl<C: IntoTrace> IntoTrace for BasePass<C, Infallible> {
    type Output = BasePass<C::Output, Infallible>;

    fn into_trace(self) -> Self::Output {
        BasePass {
            label: self.label,
            error: self.error,
            commands: self
                .commands
                .into_iter()
                .map(|cmd| cmd.into_trace())
                .collect(),
            dynamic_offsets: self.dynamic_offsets,
            string_data: self.string_data,
            immediates_data: self.immediates_data,
        }
    }
}

impl IntoTrace for ArcComputeCommand {
    type Output = ComputeCommand<PointerReferences>;
    fn into_trace(self) -> Self::Output {
        use ComputeCommand as C;
        match self {
            C::SetBindGroup {
                index,
                num_dynamic_offsets,
                bind_group,
            } => C::SetBindGroup {
                index,
                num_dynamic_offsets,
                bind_group: bind_group.map(|bg| bg.into_trace()),
            },
            C::SetPipeline(id) => C::SetPipeline(id.into_trace()),
            C::SetImmediate {
                offset,
                size_bytes,
                values_offset,
            } => C::SetImmediate {
                offset,
                size_bytes,
                values_offset,
            },
            C::Dispatch(groups) => C::Dispatch(groups),
            C::DispatchIndirect { buffer, offset } => C::DispatchIndirect {
                buffer: buffer.into_trace(),
                offset,
            },
            C::PushDebugGroup { color, len } => C::PushDebugGroup { color, len },
            C::PopDebugGroup => C::PopDebugGroup,
            C::InsertDebugMarker { color, len } => C::InsertDebugMarker { color, len },
            C::WriteTimestamp {
                query_set,
                query_index,
            } => C::WriteTimestamp {
                query_set: query_set.into_trace(),
                query_index,
            },
            C::BeginPipelineStatisticsQuery {
                query_set,
                query_index,
            } => C::BeginPipelineStatisticsQuery {
                query_set: query_set.into_trace(),
                query_index,
            },
            C::EndPipelineStatisticsQuery => C::EndPipelineStatisticsQuery,
        }
    }
}

impl IntoTrace for ArcRenderCommand {
    type Output = RenderCommand<PointerReferences>;
    fn into_trace(self) -> Self::Output {
        use RenderCommand as C;
        match self {
            C::SetBindGroup {
                index,
                num_dynamic_offsets,
                bind_group,
            } => C::SetBindGroup {
                index,
                num_dynamic_offsets,
                bind_group: bind_group.map(|bg| bg.into_trace()),
            },
            C::SetPipeline(id) => C::SetPipeline(id.into_trace()),
            C::SetIndexBuffer {
                buffer,
                index_format,
                offset,
                size,
            } => C::SetIndexBuffer {
                buffer: buffer.into_trace(),
                index_format,
                offset,
                size,
            },
            C::SetVertexBuffer {
                slot,
                buffer,
                offset,
                size,
            } => C::SetVertexBuffer {
                slot,
                buffer: buffer.into_trace(),
                offset,
                size,
            },
            C::SetBlendConstant(color) => C::SetBlendConstant(color),
            C::SetStencilReference(val) => C::SetStencilReference(val),
            C::SetViewport {
                rect,
                depth_min,
                depth_max,
            } => C::SetViewport {
                rect,
                depth_min,
                depth_max,
            },
            C::SetScissor(rect) => C::SetScissor(rect),
            C::SetImmediate {
                stages,
                offset,
                size_bytes,
                values_offset,
            } => C::SetImmediate {
                stages,
                offset,
                size_bytes,
                values_offset,
            },
            C::Draw {
                vertex_count,
                instance_count,
                first_vertex,
                first_instance,
            } => C::Draw {
                vertex_count,
                instance_count,
                first_vertex,
                first_instance,
            },
            C::DrawIndexed {
                index_count,
                instance_count,
                first_index,
                base_vertex,
                first_instance,
            } => C::DrawIndexed {
                index_count,
                instance_count,
                first_index,
                base_vertex,
                first_instance,
            },
            C::DrawMeshTasks {
                group_count_x,
                group_count_y,
                group_count_z,
            } => C::DrawMeshTasks {
                group_count_x,
                group_count_y,
                group_count_z,
            },
            C::DrawIndirect {
                buffer,
                offset,
                count,
                family,
                vertex_or_index_limit,
                instance_limit,
            } => C::DrawIndirect {
                buffer: buffer.into_trace(),
                offset,
                count,
                family,
                vertex_or_index_limit,
                instance_limit,
            },
            C::MultiDrawIndirectCount {
                buffer,
                offset,
                count_buffer,
                count_buffer_offset,
                max_count,
                family,
            } => C::MultiDrawIndirectCount {
                buffer: buffer.into_trace(),
                offset,
                count_buffer: count_buffer.into_trace(),
                count_buffer_offset,
                max_count,
                family,
            },
            C::PushDebugGroup { color, len } => C::PushDebugGroup { color, len },
            C::PopDebugGroup => C::PopDebugGroup,
            C::InsertDebugMarker { color, len } => C::InsertDebugMarker { color, len },
            C::WriteTimestamp {
                query_set,
                query_index,
            } => C::WriteTimestamp {
                query_set: query_set.into_trace(),
                query_index,
            },
            C::BeginOcclusionQuery { query_index } => C::BeginOcclusionQuery { query_index },
            C::EndOcclusionQuery => C::EndOcclusionQuery,
            C::BeginPipelineStatisticsQuery {
                query_set,
                query_index,
            } => C::BeginPipelineStatisticsQuery {
                query_set: query_set.into_trace(),
                query_index,
            },
            C::EndPipelineStatisticsQuery => C::EndPipelineStatisticsQuery,
            C::ExecuteBundle(bundle) => C::ExecuteBundle(bundle.into_trace()),
        }
    }
}

impl<'a> IntoTrace for crate::binding_model::ResolvedPipelineLayoutDescriptor<'a> {
    type Output =
        crate::binding_model::PipelineLayoutDescriptor<'a, PointerId<markers::BindGroupLayout>>;
    fn into_trace(self) -> Self::Output {
        crate::binding_model::PipelineLayoutDescriptor {
            label: self.label.map(|l| Cow::Owned(l.into_owned())),
            bind_group_layouts: self
                .bind_group_layouts
                .iter()
                .map(|bgl| bgl.to_trace())
                .collect(),
            immediates_ranges: self.immediates_ranges.clone(),
        }
    }
}

impl<'a> IntoTrace for &'_ crate::binding_model::ResolvedBindGroupDescriptor<'a> {
    type Output = TraceBindGroupDescriptor<'a>;

    fn into_trace(self) -> Self::Output {
        use crate::binding_model::{
            BindGroupEntry, BindingResource, BufferBinding, ResolvedBindingResource,
        };
        TraceBindGroupDescriptor {
            label: self.label.clone(),
            layout: self.layout.to_trace(),
            entries: Cow::Owned(
                self.entries
                    .iter()
                    .map(|entry| {
                        let resource = match &entry.resource {
                            ResolvedBindingResource::Buffer(buffer_binding) => {
                                BindingResource::Buffer(BufferBinding {
                                    buffer: buffer_binding.buffer.to_trace(),
                                    offset: buffer_binding.offset,
                                    size: buffer_binding.size,
                                })
                            }
                            ResolvedBindingResource::BufferArray(buffer_bindings) => {
                                let resolved_buffers: Vec<_> = buffer_bindings
                                    .iter()
                                    .map(|bb| BufferBinding {
                                        buffer: bb.buffer.to_trace(),
                                        offset: bb.offset,
                                        size: bb.size,
                                    })
                                    .collect();
                                BindingResource::BufferArray(Cow::Owned(resolved_buffers))
                            }
                            ResolvedBindingResource::Sampler(sampler_id) => {
                                BindingResource::Sampler(sampler_id.to_trace())
                            }
                            ResolvedBindingResource::SamplerArray(sampler_ids) => {
                                let resolved: Vec<_> =
                                    sampler_ids.iter().map(|id| id.to_trace()).collect();
                                BindingResource::SamplerArray(Cow::Owned(resolved))
                            }
                            ResolvedBindingResource::TextureView(texture_view_id) => {
                                BindingResource::TextureView(texture_view_id.to_trace())
                            }
                            ResolvedBindingResource::TextureViewArray(texture_view_ids) => {
                                let resolved: Vec<_> =
                                    texture_view_ids.iter().map(|id| id.to_trace()).collect();
                                BindingResource::TextureViewArray(Cow::Owned(resolved))
                            }
                            ResolvedBindingResource::AccelerationStructure(tlas_id) => {
                                BindingResource::AccelerationStructure(tlas_id.to_trace())
                            }
                            ResolvedBindingResource::ExternalTexture(external_texture_id) => {
                                BindingResource::ExternalTexture(external_texture_id.to_trace())
                            }
                        };
                        BindGroupEntry {
                            binding: entry.binding,
                            resource,
                        }
                    })
                    .collect(),
            ),
        }
    }
}

impl<'a> IntoTrace for crate::pipeline::ResolvedGeneralRenderPipelineDescriptor<'a> {
    type Output = TraceGeneralRenderPipelineDescriptor<'a>;

    fn into_trace(self) -> Self::Output {
        TraceGeneralRenderPipelineDescriptor {
            label: self.label,
            layout: self.layout.into_trace(),
            vertex: self.vertex.into_trace(),
            primitive: self.primitive,
            depth_stencil: self.depth_stencil,
            multisample: self.multisample,
            fragment: self.fragment.map(|f| f.into_trace()),
            multiview_mask: self.multiview_mask,
            cache: self.cache.map(|c| c.into_trace()),
        }
    }
}

impl<'a> IntoTrace for crate::pipeline::ResolvedComputePipelineDescriptor<'a> {
    type Output = TraceComputePipelineDescriptor<'a>;

    fn into_trace(self) -> Self::Output {
        TraceComputePipelineDescriptor {
            label: self.label,
            layout: self.layout.into_trace(),
            stage: self.stage.into_trace(),
            cache: self.cache.map(|c| c.into_trace()),
        }
    }
}

impl<'a> IntoTrace for crate::pipeline::ResolvedProgrammableStageDescriptor<'a> {
    type Output =
        crate::pipeline::ProgrammableStageDescriptor<'a, PointerId<markers::ShaderModule>>;
    fn into_trace(self) -> Self::Output {
        crate::pipeline::ProgrammableStageDescriptor {
            module: self.module.into_trace(),
            entry_point: self.entry_point,
            constants: self.constants,
            zero_initialize_workgroup_memory: self.zero_initialize_workgroup_memory,
        }
    }
}

impl<'a> IntoTrace
    for crate::pipeline::RenderPipelineVertexProcessor<'a, Arc<crate::pipeline::ShaderModule>>
{
    type Output =
        crate::pipeline::RenderPipelineVertexProcessor<'a, PointerId<markers::ShaderModule>>;
    fn into_trace(self) -> Self::Output {
        match self {
            crate::pipeline::RenderPipelineVertexProcessor::Vertex(vertex) => {
                crate::pipeline::RenderPipelineVertexProcessor::Vertex(vertex.into_trace())
            }
            crate::pipeline::RenderPipelineVertexProcessor::Mesh(task, mesh) => {
                crate::pipeline::RenderPipelineVertexProcessor::Mesh(
                    task.map(|t| t.into_trace()),
                    mesh.into_trace(),
                )
            }
        }
    }
}

impl<'a> IntoTrace for crate::pipeline::ResolvedTaskState<'a> {
    type Output = crate::pipeline::TaskState<'a, PointerId<markers::ShaderModule>>;
    fn into_trace(self) -> Self::Output {
        crate::pipeline::TaskState {
            stage: self.stage.into_trace(),
        }
    }
}

impl<'a> IntoTrace for crate::pipeline::ResolvedMeshState<'a> {
    type Output = crate::pipeline::MeshState<'a, PointerId<markers::ShaderModule>>;
    fn into_trace(self) -> Self::Output {
        crate::pipeline::MeshState {
            stage: self.stage.into_trace(),
        }
    }
}

impl<'a> IntoTrace for crate::pipeline::ResolvedVertexState<'a> {
    type Output = crate::pipeline::VertexState<'a, PointerId<markers::ShaderModule>>;
    fn into_trace(self) -> Self::Output {
        crate::pipeline::VertexState {
            stage: self.stage.into_trace(),
            buffers: self.buffers,
        }
    }
}

impl<'a> IntoTrace for crate::pipeline::ResolvedFragmentState<'a> {
    type Output = crate::pipeline::FragmentState<'a, PointerId<markers::ShaderModule>>;
    fn into_trace(self) -> Self::Output {
        crate::pipeline::FragmentState {
            stage: self.stage.into_trace(),
            targets: self.targets,
        }
    }
}

impl<T: IntoTrace> IntoTrace for Option<T> {
    type Output = Option<T::Output>;
    fn into_trace(self) -> Self::Output {
        self.map(|v| v.into_trace())
    }
}
